package com.dh.example.netty.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.dh.example.netty.configuration.ThreadPoolConfig;
import com.dh.example.netty.entity.User;
import com.dh.example.netty.entity.dto.WsChannelExtraDTO;
import com.dh.example.netty.entity.event.UserOfflineEvent;
import com.dh.example.netty.pojo.WsAuthorize;
import com.dh.example.netty.pojo.response.WsBaseResp;
import com.dh.example.netty.service.WebSocketService;
import com.dh.example.netty.util.ChannelUtil;
import com.dh.example.netty.util.NettyUtil;
import com.dh.example.netty.util.WsMsgBuilderAdapter;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * websocket处理类
 *
 * @author yolo
 * @date 2024/03/14 14:02:05
 */
@Component
@Slf4j
public class WebSocketServiceImpl implements WebSocketService {

    /**
     * 所有已连接的websocket连接列表和一些额外参数
     */
    private static final ConcurrentHashMap<Channel, WsChannelExtraDTO> ONLINE_WS_MAP = new ConcurrentHashMap<>();
    /**
     * 所有在线的用户和对应的socket
     */
    private static final ConcurrentHashMap<Long, CopyOnWriteArrayList<Channel>> ONLINE_UID_MAP = new ConcurrentHashMap<>();

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Resource
    @Qualifier(ThreadPoolConfig.WS_EXECUTOR)
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    /**
     * 处理登录请求
     * 处理用户登录请求，需要返回一张带code的二维码
     *
     * @param channel 频道
     */
    @SneakyThrows
    @Override
    public void handleLoginReq(Channel channel) {
        //返回给前端（channel必在本地） 生成二维码生成
        ChannelUtil.sendMsg(channel, WsMsgBuilderAdapter.buildLoginResp("http://www.baidu.com?生成二维码生成"));
    }

    @Override
    public void connect(Channel channel) {
        ONLINE_WS_MAP.put(channel, new WsChannelExtraDTO());
    }

    @Override
    public void removed(Channel channel) {
        WsChannelExtraDTO wsChannelExtraDTO = ONLINE_WS_MAP.get(channel);
        Optional<Long> uidOptional = Optional.ofNullable(wsChannelExtraDTO)
                .map(WsChannelExtraDTO::getUid);
        boolean offlineAll = offline(channel, uidOptional);
        //已登录用户断连,并且全下线成功
        if (uidOptional.isPresent() && offlineAll) {
            User user = new User();
            user.setId(uidOptional.get());
            user.setLastOptTime(new Date());
            applicationEventPublisher.publishEvent(new UserOfflineEvent(this, user));
        }
    }

    @Override
    public void authorize(Channel channel, WsAuthorize wsAuthorize) {
        //校验token
        boolean verifySuccess = "token".equals(wsAuthorize.getToken());
        if (verifySuccess) {
            //用户校验成功给用户登录
            User user = getAdminUser();
            Long attr = NettyUtil.getAttr(channel, NettyUtil.USER_ID);
            if (attr != null) {
                user.setId(attr);
            }
            loginSuccess(channel, user, wsAuthorize.getToken());
            return;
        }
        //让前端的token失效
        ChannelUtil.sendMsg(channel, WsMsgBuilderAdapter.buildInvalidateTokenResp());
    }

    /**
     * (channel必在本地)登录成功，并更新状态
     */
    private void loginSuccess(Channel channel, User user, String token) {
        //更新上线列表
        online(channel, user.getId());
        //发送给对应的用户
        ChannelUtil.sendMsg(channel, WsMsgBuilderAdapter.buildLoginSuccessResp(user, token, true));
    }

    /**
     * 用户上线
     */
    private void online(Channel channel, Long uid) {
        getOrInitChannelExt(channel).setUid(uid);
        ONLINE_UID_MAP.putIfAbsent(uid, new CopyOnWriteArrayList<>());
        ONLINE_UID_MAP.get(uid).add(channel);
        NettyUtil.setAttr(channel, NettyUtil.USER_ID, uid);
    }

    /**
     * 用户下线
     * return 是否全下线成功
     */
    private boolean offline(Channel channel, Optional<Long> uidOptional) {
        ONLINE_WS_MAP.remove(channel);
        if (uidOptional.isPresent()) {
            CopyOnWriteArrayList<Channel> channels = ONLINE_UID_MAP.get(uidOptional.get());
            if (CollectionUtil.isNotEmpty(channels)) {
                channels.removeIf(ch -> Objects.equals(ch, channel));
            }
            return CollectionUtil.isEmpty(ONLINE_UID_MAP.get(uidOptional.get()));
        }
        return true;
    }



    private User getAdminUser() {
        User user = new User();
        user.setId(1L);
        user.setLastOptTime(new Date());
        user.setName("Admin");
        return user;
    }

    /**
     * 如果在线列表不存在，就先把该channel放进在线列表
     *
     * @param channel
     * @return
     */
    private WsChannelExtraDTO getOrInitChannelExt(Channel channel) {
        WsChannelExtraDTO wsChannelExtraDTO =
                ONLINE_WS_MAP.getOrDefault(channel, new WsChannelExtraDTO());
        WsChannelExtraDTO old = ONLINE_WS_MAP.putIfAbsent(channel, wsChannelExtraDTO);
        return ObjectUtil.isNull(old) ? wsChannelExtraDTO : old;
    }


    @Override
    public void sendToAllOnline(WsBaseResp<?> wsBaseResp) {
        ONLINE_WS_MAP.forEach((channel, ext) -> threadPoolTaskExecutor.execute(() -> ChannelUtil.sendMsg(channel, wsBaseResp)));
    }

    @Override
    public void sendToUid(WsBaseResp<?> wsBaseResp, Long uid) {
        CopyOnWriteArrayList<Channel> channels = ONLINE_UID_MAP.get(uid);
        if (CollectionUtil.isEmpty(channels)) {
            log.info("用户：{}不在线", uid);
            return;
        }
        channels.forEach(channel -> {
            threadPoolTaskExecutor.execute(() -> ChannelUtil.sendMsg(channel, wsBaseResp));
        });
    }

}
